package com.krolik22.maven.repgen.modules;

import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactCollector;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.siterenderer.Renderer;
import org.apache.maven.doxia.tools.SiteTool;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.AbstractMavenReportRenderer;
import org.apache.maven.reporting.MavenReportException;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.util.*;

/**
 * @goal pkgs
 * @phase site
 */
public class PackagesReport extends AbstractMavenReport {
    /**
     * The enclosing project.
     *
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    protected MavenProject project;

    /**
     * SiteTool component.
     *
     * @component
     * @since 2.1
     */
    protected SiteTool siteTool;

    /**
     * The projects in the reactor for aggregation report. <p/> Note: This is
     * passed by Maven and must not be configured by the user. </p>
     *
     * @parameter expression="${reactorProjects}"
     * @readonly
     */
    private List<MavenProject> reactorProjects;

    /**
     * The artifact factory to use.
     *
     * @component
     * @required
     * @readonly
     */
    private ArtifactFactory artifactFactory;

    /**
     * The artifact metadata source to use.
     *
     * @component
     * @required
     * @readonly
     */
    private ArtifactMetadataSource artifactMetadataSource;

    /**
     * The artifact collector to use.
     *
     * @component
     * @required
     * @readonly
     */
    private ArtifactCollector artifactCollector;


    /**
     * The dependency tree builder to use.
     *
     * @component
     * @required
     * @readonly
     */
    private DependencyTreeBuilder dependencyTreeBuilder;


    /**
     * The artifact repository to use.
     *
     * @parameter expression="${localRepository}"
     * @required
     * @readonly
     */
    private ArtifactRepository localRepository;

    /**
     * @parameter default-value="${project.remoteArtifactRepositories}"
     * @required
     * @readonly
     */
    private List<?> remoteRepositories;

    /**
     * @parameter
     */
    private String pkgsDepGroups;

    /**
     * Directory where reports will go.
     *
     * @parameter expression="${project.reporting.outputDirectory}"
     * @required
     * @readonly
     */
    private String outputDirectory;

    /**
     * Generates the site report.
     *
     * @component
     */
    private Renderer siteRenderer;

    /**
     * @parameter
     */
    private String wikiUrlTemplate;


    protected Renderer getSiteRenderer() {
        return siteRenderer;
    }

    protected String getOutputDirectory() {
        return outputDirectory;
    }

    protected MavenProject getProject() {
        return project;
    }

    public String getOutputName() {
        return "pkgs";
    }

    public String getName(Locale locale) {
        return "Packages";
    }

    public String getDescription(Locale locale) {
        return "Packages of the Project";
    }

    @Override
    public boolean canGenerateReport() {
        return getProject().getModel().getModules() != null && !getProject().getModel().getModules().isEmpty();
    }

    @Override
    protected void executeReport(Locale locale) throws MavenReportException {
        if (!canGenerateReport()) {
            return;
        }
        new PackageRenderer(getSink(), getProject().getModel(), locale).render();
    }

    // ----------------------------------------------------------------------
    // Private
    // ----------------------------------------------------------------------

    /**
     * Internal renderer class
     */
    private class PackageRenderer extends AbstractMavenReportRenderer {
        private Model model;

        PackageRenderer(Sink sink, Model model, Locale locale) {
            super(sink);

            this.model = model;
        }

        @Override
        public String getTitle() {
            return "Packages";
        }

        @Override
        public void renderBody() {
            List<String> modules = model.getModules();
            startSection(getTitle());
            startTable();
            tableHeader(new String[]{"Maven Project", "Wiki", "Package", "ENV package", "Description", "Dependencies"});

            List<String> depGroups = Arrays.asList(pkgsDepGroups.split(","));

            for (String module : modules) {
                Model moduleModel;
                File f = new File(project.getBasedir(), module + "/pom.xml");

                if (f.exists()) {
                    moduleModel = readModel(f);
                    if (moduleModel == null) {
                        getLog().warn("Unable to read filesystem POM for module " + module);
                        continue;
                    }
                } else {
                    continue;
                }

                if (moduleModel.getParent() == null || !moduleModel.getParent().getArtifactId().endsWith("-pkg-maven-component")) {
                    continue;
                }

                final String moduleName = moduleModel.getName();
                String link = module + "/index.html";

                if (!getProject().getId().equals(moduleModel.getParent().getId())) {
                    if (!module.contains(moduleName)) {
                        continue;
                    }
                    String basePath = module.substring(0, module.indexOf(moduleName));
                    if (basePath.length() == 0) {
                        link = moduleModel.getParent().getArtifactId() + "/" + moduleName + "/index.html";
                    } else {
                        link = basePath + moduleModel.getParent().getArtifactId() + "/" + moduleName + "/index.html";
                    }
                }


                // find dependencies
                LinkedHashMap<String, List<Dependency>> deps = new LinkedHashMap<String, List<Dependency>>();
                for (Object o : moduleModel.getDependencies()) {
                    Dependency dependency = (Dependency) o;

                    if (depGroups.contains(dependency.getGroupId())) {
                        List<Dependency> list = deps.get(dependency.getGroupId());
                        if (list == null) {
                            list = new ArrayList<Dependency>();
                            deps.put(dependency.getGroupId(), list);
                        }
                        if (!list.contains(dependency)) {
                            list.add(dependency);
                        }
                    }
                }

                //todo refactor
                StringBuilder depLinks = new StringBuilder();
                String env_component = "";
                for (Map.Entry<String, List<Dependency>> entry : deps.entrySet()) {
                    List<Dependency> list = entry.getValue();
                    for (Dependency dependency : list) {
                        if (modules.contains(dependency.getArtifactId())) {

                            File depFile = new File(project.getBasedir(), dependency.getArtifactId() + "/pom.xml");
                            Model depModuleModel;
                            if (depFile.exists()) {
                                depModuleModel = readModel(depFile);
                                if (depModuleModel == null) {
                                    getLog().warn("Unable to read filesystem POM for module " + module);
                                    continue;
                                }
                            } else {
                                continue;
                            }

                            env_component = depModuleModel.getProperties().getProperty("component");
                            String depHref = depModuleModel.getArtifactId() + "/index.html";
                            if (!getProject().getId().equals(depModuleModel.getParent().getId())) {
                                depHref = depModuleModel.getParent().getArtifactId() + "/" + depModuleModel.getArtifactId() + "/index.html";
                            }

                            if (depLinks.length() > 0) {
                                depLinks.append(" ");
                            }
                            depLinks.append(linkedName(dependency.getArtifactId(), depHref));
                        }

                    }
                }

                if (moduleModel.getProperties().containsKey("component")) {
                    env_component = moduleModel.getProperties().getProperty("component");
                }
                String pkg_name = moduleModel.getProperties().getProperty("pkg.name");

                String wikiUrl = wikiUrlTemplate.replaceAll("\\$\\{wiki.pageName\\}", moduleName);
                tableRow(new String[]{
                        linkedName(moduleName, link),
                        "{wiki, " + wikiUrl + "}",
                        pkg_name,
                        env_component,
                        moduleModel.getDescription(),
                        depLinks.toString()
                });

            }

            endTable();
            endSection();
        }
    }

    private String linkedName(String name, String link) {
        return "{" + name + ", ./" + link + "}";
    }

    /**
     * Gets the pom model for this file.
     *
     * @param pom the pom
     * @return the model
     */
    private Model readModel(File pom) {
        MavenXpp3Reader xpp3 = new MavenXpp3Reader();
        Reader reader = null;

        try {
            reader = ReaderFactory.newXmlReader(pom);
            return xpp3.read(reader);
        } catch (IOException io) {
            getLog().debug(io);
            return null;
        } catch (XmlPullParserException xe) {
            getLog().debug(xe);
            return null;
        } finally {
            IOUtil.close(reader);
        }
    }
}
